package synthlabgui.widgets;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.GeneralPath;
import java.util.Map;

import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

import synthlab.api.ModuleFactory;
import synthlab.api.ModulePool;
import synthlab.api.ModulePoolFactory;
import synthlab.api.Scheduler;
import synthlab.api.SchedulerFactory;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

public class ModulePoolPanel extends JPanel implements MouseListener,
	MouseMotionListener, DropTargetListener {
    private static final long serialVersionUID = 1943028163107894975L;

    private MainWindow parentPanel;

    public ModulePoolPanel(MainWindow parent) {
	super();
	this.parentPanel = parent;
	setupGeneral();
	pool_ = ModulePoolFactory.createDefault();
	scheduler_ = SchedulerFactory.createDefault();
	scheduler_.setPool(pool_);
	scheduler_.play(0);
	setTransferHandler(new ModuleTransferHandler());
	try {
	    getDropTarget().addDropTargetListener(this);
	} catch (Exception e) {
	    e.printStackTrace();
	}
	links_ = HashBiMap.create();
    }

    public void addModule(Module module) {
	synthlab.api.Module model = ModuleFactory.createFromPrototype(module
		.getWrapped());
	Module m = new Module(model.getName() + "-"
		+ parentPanel.getRegistryPanel().getCounter(model.getName()),
		model, this);
	pool_.register(m.getWrapped());
	add(m);
	parentPanel.getRegistryPanel().increaseCounter(model.getName());
	m.setBounds(dropPosition_.x - 100, dropPosition_.y - 20,
		m.getBounds().width, m.getBounds().height);
    }

    public void removeModule(Module module) {
	for (PortHandler ph : module.getPorts()) {
	    unlink(ph);
	}
	pool_.unregister(module.getWrapped());
	module.close();
	remove(module);
	repaint();
    }

    public MainWindow getParentPanel() {
	return parentPanel;
    }

    public void paint(Graphics g) {
	// Enable anti-aliasing
	RenderingHints renderHints = new RenderingHints(
		RenderingHints.KEY_ANTIALIASING,
		RenderingHints.VALUE_ANTIALIAS_ON);
	renderHints.put(RenderingHints.KEY_RENDERING,
		RenderingHints.VALUE_RENDER_QUALITY);
	((Graphics2D) g).setRenderingHints(renderHints);
	// Draw background
	g.drawImage(background_, getWidth() / 2 - background_.getWidth(null)
		/ 2, getHeight() / 2 - background_.getHeight(null) / 2, null);
	// Draw links
	drawLinks(g);
	if (linking_) {
	    drawLink(g, linkStart_.x, linkStart_.y, linkCurrent_.x,
		    linkCurrent_.y, Color.yellow);
	}
	// Render modules
	super.paint(g);
    }

    public boolean isLinking() {
	return linking_;
    }

    private void drawLinks(Graphics g) {
	selectedLink_ = null;
	for (Map.Entry<PortHandler, PortHandler> entry : links_.entrySet()) {
	    int x0 = (int) (entry.getKey().getBounds().getX() + entry.getKey()
		    .getParent().getBounds().getX());
	    int y0 = (int) (entry.getKey().getBounds().getY() + entry.getKey()
		    .getParent().getBounds().getY()) + 6;
	    int x1 = (int) (entry.getValue().getBounds().getX() + entry
		    .getValue().getParent().getBounds().getX());
	    int y1 = (int) (entry.getValue().getBounds().getY() + entry
		    .getValue().getParent().getBounds().getY()) + 6;
	    boolean selected = linkContains(x0, y0, x1, y1, linkCurrent_);
	    drawLink(g, x0, y0, x1, y1, selected ? Color.red : Color.yellow);
	    if (selected)
		selectedLink_ = entry;
	}
    }

    private void drawLink(Graphics g, int x0, int y0, int x1, int y1, Color c) {
	Graphics2D g2 = (Graphics2D) g;
	// Create the draw path
	GeneralPath drawPath = new GeneralPath();
	drawPath.moveTo(x0, y0);
	drawPath.curveTo(x0 + 100, y0, x1 - 100, y1, x1, y1);
	// Draw external link (gray)
	g2.setStroke(new BasicStroke(5));
	g2.setColor(Color.darkGray);
	g2.draw(drawPath);
	// Draw internal link (yellow) - or red if selected
	g2.setStroke(new BasicStroke(3));
	g2.setColor(c);
	;
	g2.draw(drawPath);
	// Restore standard stroke and color
	g.setColor(Color.black);
	g2.setStroke(new BasicStroke(1));
    }

    private boolean linkContains(int x0, int y0, int x1, int y1, Point p) {
	int marginX = 5;
	int marginY = 5;
	GeneralPath selectPathX = new GeneralPath();
	GeneralPath selectPathY = new GeneralPath();
	x0 += marginX;
	x1 += marginX;
	selectPathX.moveTo(x0, y0);
	selectPathX.curveTo(x0 + 100, y0, x1 - 100, y1, x1, y1);
	x0 -= marginX * 2;
	x1 -= marginX * 2;
	selectPathX.lineTo(x1, y1);
	selectPathX.curveTo(x1 - 100, y1, x0 + 100, y0, x0, y0);
	selectPathX.closePath();
	x0 += marginX;
	x1 += marginX;
	y0 += marginY;
	y1 += marginY;
	selectPathY.moveTo(x0, y0);
	selectPathY.curveTo(x0 + 100, y0, x1 - 100, y1, x1, y1);
	y0 -= marginY * 2;
	y1 -= marginY * 2;
	selectPathY.lineTo(x1, y1);
	selectPathY.curveTo(x1 - 100, y1, x0 + 100, y0, x0, y0);
	selectPathY.closePath();
	return selectPathX.contains(p) || selectPathY.contains(p);
    }

    private void setupGeneral() {
	setLayout(null);
	setBackground(Color.white);
	addMouseListener(this);
	addMouseMotionListener(this);
	setOpaque(false);
	background_ = new ImageIcon("background.jpg").getImage();
	linking_ = false;
	setVisible(true);
    }

    public void updateLinks() {
    }

    @Override
    public void mouseDragged(MouseEvent e) {
    }

    @Override
    public void mouseMoved(MouseEvent e) {
	linkCurrent_ = e.getPoint();
	repaint();
    }

    @Override
    public void mouseClicked(MouseEvent e) {
	if (selectedLink_ != null && e.getButton() == MouseEvent.BUTTON3) {
	    unlink(selectedLink_.getValue());
	}
    }

    @Override
    public void mouseEntered(MouseEvent arg0) {
    }

    @Override
    public void mouseExited(MouseEvent arg0) {
    }

    @Override
    public void mousePressed(MouseEvent arg0) {
    }

    @Override
    public void mouseReleased(MouseEvent arg0) {
	endDrag(null, null);
    }

    @Override
    public void dragEnter(DropTargetDragEvent arg0) {
    }

    @Override
    public void dragExit(DropTargetEvent arg0) {
    }

    @Override
    public void dragOver(DropTargetDragEvent e) {
	dropPosition_ = e.getLocation();
    }

    @Override
    public void drop(DropTargetDropEvent e) {
    }

    @Override
    public void dropActionChanged(DropTargetDragEvent arg0) {
    }

    public void updateDrag(Module module, PortHandler port, Point e, Point p) {
	linking_ = true;
	linkStart_ = e;
	linkCurrent_ = p;
	portStart_ = port;
	repaint();
    }

    public void updateDrag(Module module, PortHandler port, Point e, Point p,
	    boolean reversed) {
	linking_ = true;
	linkStart_ = e;
	linkCurrent_ = p;
	portStart_ = port;
	reversed_ = reversed;
	repaint();
    }

    public PortHandler getPortStart() {
	return portStart_;
    }

    public void endDrag(Module module, PortHandler port) {
	linking_ = false;
	repaint();
	if (module == null || port == null)
	    return;
	Component c;
	if (!reversed_)
	    c = findComponentAt(linkCurrent_);
	else
	    c = findComponentAt(linkStart_);
	if (c.getClass().equals(PortHandler.class)) {
	    link(portStart_, (PortHandler) c);
	}
    }

    public void link(PortHandler output, PortHandler input) {
	// Sanity check: should be output & input
	if ((output.getWrapped().isInput() && input.getWrapped().isInput())
		|| (output.getWrapped().isOutput() && input.getWrapped()
			.isOutput())) {
	    return;
	}
	// Check order of input/output
	PortHandler i;
	PortHandler o;
	if (output.getWrapped().isInput()) {
	    i = output;
	    o = input;
	} else {
	    i = input;
	    o = output;
	}
	if (o.getWrapped().isLinked()) {
	    String message = "The port \""
		    + o.getWrapped().getName()
		    + "\" is already linked to an other port.\nRemove this previous link?";
	    int result = JOptionPane.showConfirmDialog(this, message,
		    "Linking problem", JOptionPane.YES_NO_OPTION);
	    if (result == JOptionPane.NO_OPTION)
		return;
	    else {
		unlink(o);
	    }
	}
	if (i.getWrapped().isLinked()) {
	    String message = "The port \""
		    + i.getWrapped().getName()
		    + "\" is already linked to an other port.\nRemove this previous link?";
	    int result = JOptionPane.showConfirmDialog(this, message,
		    "Linking problem", JOptionPane.YES_NO_OPTION);
	    if (result == JOptionPane.NO_OPTION)
		return;
	    else {
		unlink(i);
	    }
	}
	pool_.link(o.getWrapped(), i.getWrapped());
	links_.put(o, i);
	// actualiser ihm
	((Module) (i.getParent())).getConfigWindow().refresh();
    }

    public void unlink(PortHandler port) {
	PortHandler port2 = links_.get(port);
	if (port2 != null) {
	    pool_.unlink(port.getWrapped(), links_.get(port).getWrapped());
	    links_.remove(port);
	    ((Module) (port2.getParent())).getConfigWindow().refresh();
	} else {
	    port2 = links_.inverse().get(port);
	    if (port2 != null) {
		pool_
			.unlink(port2.getWrapped(), links_.get(port2)
				.getWrapped());
		links_.remove(port2);
		((Module) (port.getParent())).getConfigWindow().refresh();
	    }
	}
    }

    public Scheduler getScheduler() {
	return scheduler_;
    }

    private boolean reversed_;

    private Scheduler scheduler_;

    private Map.Entry<PortHandler, PortHandler> selectedLink_;

    private ModulePool pool_;

    private Image background_;

    private Point dropPosition_;

    private boolean linking_;

    private Point linkStart_;

    private Point linkCurrent_;

    private PortHandler portStart_;

    private BiMap<PortHandler, PortHandler> links_;
}
